package business;

import java.util.ArrayList;
import java.util.List;

import node.AAdvice;
import node.AAdviceComponentAspect;
import node.AAdvicePointcutComponentAspect;
import node.ADeclarationIntertype;
import node.ADesignatorStartAdvicePointcutSameLine;
import node.ADesignatorStartPointcut;
import node.AFunctionIdStartAdvicePointcutSameLine;
import node.AFunctionIdStartPointcut;
import node.AFunctionIntertype;
import node.AGlobalVarIntertype;
import node.AIdentifierJockerPointcutIdentifierPointcutTypes;
import node.AIdentifierPointcutIdentifierPointcutTypes;
import node.AIntertypeComponentAspect;
import node.ALocalVarIntertype;
import node.APointcutAddDesignator;
import node.APointcutAddFunctionRefering;
import node.APointcutComponentAspect;
import node.APointcutFunctionRefering;
import node.PAdvicePointcutSameLine;
import node.PComponentAspect;
import node.PIntertype;
import node.PPointcut;
import node.PPointcutFunctionRefering;
import util.AdviceTimesToAct;
import util.Globals;
import util.IntertypeType;
import util.LogicalOperators;
import util.PointcutDesignator;
import util.PointcutTypes;
import exception.AdviceHasntPointcutAssociatedOnAspectException;
import exception.FunctionDeclarationIntertypeAlreadyExistsOnAspectException;
import exception.VarDeclarationIntertypeAlreadyExistsOnAspectException;

public class Aspect {
	private String identifier;
	private AspectComponents aspectComponents;
	private List<PComponentAspect> listAspectComponentFromInputFile;

	public Aspect(AspectComponents aspectComponents){
		this.aspectComponents = aspectComponents;
	}
	
	public Aspect(String identifier, List<PComponentAspect> listAspectComponentFromInputFile) throws FunctionDeclarationIntertypeAlreadyExistsOnAspectException, VarDeclarationIntertypeAlreadyExistsOnAspectException, AdviceHasntPointcutAssociatedOnAspectException{
		this.identifier = identifier;
		this.listAspectComponentFromInputFile = listAspectComponentFromInputFile;

		/* Fill the aspect component list to manipulate later. */
		fillAspectComponentListToManipulate();
	}

	/**
	 * Extracts a components aspect and puts it on the correct list.
	 * Checks if these components aspect arent duplicate on the aspect file.
	 * 
	 * @throws FunctionDeclarationIntertypeAlreadyExistsOnAspectException
	 * @throws VarDeclarationIntertypeAlreadyExistsOnAspectException
	 * @throws AdviceHasntPointcutAssociatedOnAspectException
	 */
	private void fillAspectComponentListToManipulate() throws FunctionDeclarationIntertypeAlreadyExistsOnAspectException, VarDeclarationIntertypeAlreadyExistsOnAspectException, AdviceHasntPointcutAssociatedOnAspectException{
		/* Creates lists to put different component aspect type inside. */
		List<Advice> adviceList = new ArrayList<Advice>();
		List<Pointcut> pointcutList = new ArrayList<Pointcut>();
		List<Intertype> intertypeList = new ArrayList<Intertype>();
		List<AdvicePointcut> advicePointcutList = new ArrayList<AdvicePointcut>();

		if(listAspectComponentFromInputFile != null && !listAspectComponentFromInputFile.isEmpty()){
			for(PComponentAspect componentAspectFromInputFile : listAspectComponentFromInputFile){
				if(componentAspectFromInputFile instanceof AAdviceComponentAspect){
					/* If the component received is a advice, create a Advice Object type and puts it on correct list. */
					AAdvice adviceComponent = ((AAdvice)((AAdviceComponentAspect)componentAspectFromInputFile).getAdvice());
					adviceList.add(createAdvice(adviceComponent));

				} else if(componentAspectFromInputFile instanceof APointcutComponentAspect){
					/* If the component received is a pointcut, create a Pointcut Object type and puts it on correct list. */
					PPointcut pointcutComponent = ((APointcutComponentAspect)componentAspectFromInputFile).getPointcut();
					pointcutList.add(createPointcut(pointcutComponent));

				} else if(componentAspectFromInputFile instanceof AIntertypeComponentAspect){
					/* If the component received is a intertype, create a Intertype Object type and puts it on correct list. */
					PIntertype intertype = ((AIntertypeComponentAspect)componentAspectFromInputFile).getIntertype();
					intertypeList.add(createIntertype(intertype));

				} else {
					/* If the component received is a advicePointcut, create a AdvicePointcut Object type and puts it on correct list. */
					PAdvicePointcutSameLine advicePointcutComponent = ((AAdvicePointcutComponentAspect)componentAspectFromInputFile).getAdvicePointcutSameLine();
					advicePointcutList.add(createAdvicePointcut(advicePointcutComponent));
				} 
			}
		}

		/* Creates a aspect component object with the filled list. */
		this.aspectComponents = new AspectComponents(adviceList, pointcutList, intertypeList, advicePointcutList);

		/* Verifies if exists aspect components duplicated. */
		this.aspectComponents.makeSemanticAnalysisOnAspectComponents(identifier);
	}

	/**
	 * Create a advice, and sets all of its fields.
	 * 
	 * @param adviceComponent
	 * @return
	 */
	private Advice createAdvice(AAdvice adviceComponent){

		/* Gets the advice time. */
		String adviceComponentTime = adviceComponent.getAdviceSpec().getText();
		AdviceTimesToAct timeToAct = Globals.extractAdviceComponentType(adviceComponentTime);

		/* Gets the pointcut identifier refering. */ 
		String pointcutIdentifier = adviceComponent.getIdentifier().getText();
		
		/* Gets more pointcut identifier refering (if exists). */ 
		String othersPointcutIdentifiers = null;
		if(adviceComponent.getAddIdAdvice()!=null){
			othersPointcutIdentifiers = adviceComponent.getAddIdAdvice().toString();
		}

		/* Gets the body function to add on code. */ 
		String bodyFunction = adviceComponent.getBlock().toString();

		/* Creates a advice object and returns it. */
		return new Advice(timeToAct, bodyFunction, pointcutIdentifier, othersPointcutIdentifiers);
	}

	/**
	 * Create a pointcut, and sets all of its fields.
	 * 
	 * @param pointcutComponent
	 * @return
	 */
	private Pointcut createPointcut(PPointcut pointcutComponent){
		String designator = null;
		PointcutTypes type = null;
		String operator = null;
		String functionFormals = null;
		String functionIdentifier = null;
		String pointcutIdentifier = null;

		/* Checks the advice pointcut type (start designator, or start function). */
		if(pointcutComponent instanceof AFunctionIdStartPointcut){ 

			/* Cast to the correct object. */
			AFunctionIdStartPointcut functionIdStartPointcut = (AFunctionIdStartPointcut) pointcutComponent;

			/* Gets the pointcut identifier. */
			pointcutIdentifier = functionIdStartPointcut.getIdentifier().getText();

			/* Cast to the APointcutFunctionRefering object. */
			APointcutFunctionRefering functionRefering = (APointcutFunctionRefering) functionIdStartPointcut.getPointcutFunctionRefering();

			/* Gets the poincut type. */
			String pointcutType = functionRefering.getTypePointcutSignature().getText();
			type = Globals.extractPointcutType(pointcutType);

			/* Gets the function identifier. */
			if(functionRefering.getIdentifierPointcutTypes() instanceof AIdentifierJockerPointcutIdentifierPointcutTypes){
				functionIdentifier = ((AIdentifierJockerPointcutIdentifierPointcutTypes) functionRefering.getIdentifierPointcutTypes()).getIdentifierJocker().getText();
			}else{
				functionIdentifier = ((AIdentifierPointcutIdentifierPointcutTypes) functionRefering.getIdentifierPointcutTypes()).getIdentifier().getText();
			}

			/* Gets the formals function identifier. */
			if(functionRefering.getFormals()!=null){
				functionFormals = functionRefering.getFormals().toString();
			}

			/* If exists designators on pointcut do: */
			APointcutAddDesignator pointcutDesignator = (APointcutAddDesignator) functionIdStartPointcut.getPointcutAddDesignator();
			if(pointcutDesignator!=null){
				/* Gets the designator. */
				designator = pointcutDesignator.getDesignator().getText();
				/* Gets the operator related. */
				operator = pointcutDesignator.getLogicalOperator().getText();
			}
		}else{
			/* Cast to the correct object. */
			ADesignatorStartPointcut designatorStartPointcut = (ADesignatorStartPointcut) pointcutComponent;

			/* Gets the pointcut identifier. */
			pointcutIdentifier = designatorStartPointcut.getIdentifier().getText();

			/* Gets the designator. */
			designator = designatorStartPointcut.getDesignator().getText();
			
			/* Cast to the APointcutAddFunctionRefering object. */
			APointcutAddFunctionRefering functionRefering = (APointcutAddFunctionRefering) designatorStartPointcut.getPointcutAddFunctionRefering();

			/* If exists functionRefering on pointcut do: */
			if(functionRefering!=null){
				/* Gets the logical operator. */
			    operator = functionRefering.getLogicalOperator().getText();
			    
			    APointcutFunctionRefering functionComponents = (APointcutFunctionRefering) functionRefering.getPointcutFunctionRefering();
			    if(functionComponents!=null){
			    	/* Gets the poincut type. */
					String pointcutType = functionComponents.getTypePointcutSignature().getText();
					type = Globals.extractPointcutType(pointcutType); 
					
					/* Gets the function identifier. */
					if(functionComponents.getIdentifierPointcutTypes() instanceof AIdentifierJockerPointcutIdentifierPointcutTypes){
						functionIdentifier = ((AIdentifierJockerPointcutIdentifierPointcutTypes) functionComponents.getIdentifierPointcutTypes()).getIdentifierJocker().getText();
					}else{
						functionIdentifier = ((AIdentifierPointcutIdentifierPointcutTypes) functionComponents.getIdentifierPointcutTypes()).getIdentifier().getText();
					}
					
					/* Gets the formals function identifier. */
					if(functionComponents.getFormals()!=null){
						functionFormals = functionComponents.getFormals().toString();
					}
			    }
			}
		}
		
		String targetCode = null;
		PointcutDesignator pointcutDesignator = null;
		/* Gets the target code refering. */
		if(designator != null){
			pointcutDesignator = PointcutDesignator.TARGET;
			targetCode = Globals.extractTargetCode(designator);
		}
		
		LogicalOperators logicalOperator = null;
		/* Gets the target code refering. */
		if(operator != null){
			logicalOperator = Globals.extractLogicalOperator(operator);
		}

		/* Creates a pointcut object and returns it. */
		return new Pointcut(pointcutIdentifier, type, functionIdentifier, new Formals(functionFormals), targetCode, pointcutDesignator, logicalOperator);
	}

	/**
	 * Create a pointcut, and sets all of its fields.
	 * 
	 * @param advicePointcutComponent
	 * @return
	 */
	private AdvicePointcut createAdvicePointcut(PAdvicePointcutSameLine advicePointcutComponent){

		AdviceTimesToAct timeToAct = null;
		String designator = null;
		PointcutTypes type = null;
		String bodyFunction = null;
		String operator = null;
		String functionFormals = null;
		String functionIdentifier = null;

		/* Checks the advice pointcut type (start designator, or start function). */
		if(advicePointcutComponent instanceof ADesignatorStartAdvicePointcutSameLine){ 

			/* Cast to the correct object. */
			ADesignatorStartAdvicePointcutSameLine designatorStart = (ADesignatorStartAdvicePointcutSameLine) advicePointcutComponent;

			/* Gets the advice time. */
			String adviceComponentTime = designatorStart.getAdviceSpec().getText();
			timeToAct = Globals.extractAdviceComponentType(adviceComponentTime);

			/* Gets the body function to add on code. */
			bodyFunction = designatorStart.getBlock().toString();

			/* Gets the target designator. */
			designator = designatorStart.getDesignator().toString();

			/* Gets the informations of the pointcut designator (if it exists). */
			APointcutAddFunctionRefering addedFunction = ((APointcutAddFunctionRefering) designatorStart.getPointcutAddFunctionRefering());
			if(addedFunction!=null){

				/* Gets the logical operator. */
				operator = addedFunction.getLogicalOperator().toString();

				/* Cast to the APointcutFunctionRefering object. */
				PPointcutFunctionRefering functionRefering = addedFunction.getPointcutFunctionRefering();
				APointcutFunctionRefering functionComponent = ((APointcutFunctionRefering) functionRefering);

				/* Gets the identifier function refering. */
				functionIdentifier = functionComponent.getIdentifierPointcutTypes().toString();

				/* Gets the formals identifier function refering. */
				if(functionComponent.getFormals()!=null){
					functionFormals = functionComponent.getFormals().toString();
				}

				/* Gets the pointcut type. */
				String functionReferingType = functionComponent.getTypePointcutSignature().toString();
				type = Globals.extractPointcutType(functionReferingType);
			}

		}else{
			/* Cast to the correct object. */
			AFunctionIdStartAdvicePointcutSameLine functionStart = (AFunctionIdStartAdvicePointcutSameLine) advicePointcutComponent;

			/* Gets the advice time. */
			String adviceComponentTime = functionStart.getAdviceSpec().getText();
			timeToAct = Globals.extractAdviceComponentType(adviceComponentTime);

			/* Gets the body function to add on code. */
			bodyFunction = functionStart.getBlock().toString();

			/* Cast to the PPointcutFunctionRefering object. */
			PPointcutFunctionRefering functionRefering = ((APointcutFunctionRefering) functionStart.getPointcutFunctionRefering());

			/* Gets the identifier function refering. */
			functionIdentifier = ((APointcutFunctionRefering)functionRefering).getIdentifierPointcutTypes().toString();

			/* Gets the formals identifier function refering. */
			if(((APointcutFunctionRefering)functionRefering).getFormals()!=null){
				functionFormals = ((APointcutFunctionRefering)functionRefering).getFormals().toString();
			}

			/* Gets the pointcut type. */
			String functionReferingType = ((APointcutFunctionRefering)functionRefering).getTypePointcutSignature().toString();
			type = Globals.extractPointcutType(functionReferingType);

			APointcutAddDesignator pointcutAddDesignator = ((APointcutAddDesignator)functionStart.getPointcutAddDesignator());
			if(pointcutAddDesignator.getDesignator()!=null){
				/* Gets the target designator. */
				designator = pointcutAddDesignator.getDesignator().getText();

				/* Gets the logical operator. */
				operator = pointcutAddDesignator.getLogicalOperator().getText();
			}
		}

		String targetCode = null;
		PointcutDesignator pointcutDesignator = null;
		/* Gets the target code refering. */
		if(designator != null){
			pointcutDesignator = PointcutDesignator.TARGET;
			targetCode = Globals.extractTargetCode(designator);
		}

		LogicalOperators logicalOperator = null;
		/* Gets the target code refering. */
		if(operator != null){
			logicalOperator = Globals.extractLogicalOperator(operator);
		}

		/* Creates a advice pointcut object and returns it. */
		return new AdvicePointcut(timeToAct, bodyFunction, type, targetCode, functionIdentifier, new Formals(functionFormals),logicalOperator, pointcutDesignator);
	}


	/**
	 * Create a different types of intertype, and sets its fields.
	 * 
	 * There is three intertype types:
	 * -Function, global var, local var.
	 * 
	 * @param intertype
	 * @return
	 */
	public Intertype createIntertype(PIntertype intertype){
		IntertypeType type = null;
		String codeToInclude = null;
		String placeToInclude = null;
		String placeToIncludeFormals = null;
		
		if(intertype instanceof AFunctionIntertype){ 
			/* Cast to the correct object. */
			AFunctionIntertype functionItertype = (AFunctionIntertype) intertype;
			
			/* Gets the code to include on netlogo code. */
			codeToInclude = functionItertype.getProcedures().toString();
			
			/* Sets the intertype type. */
			type = IntertypeType.FUNCTION;
			
		}else if(intertype instanceof ADeclarationIntertype){ 
			/* Cast to the correct object. */
			ADeclarationIntertype functionItertype = (ADeclarationIntertype) intertype;
			
			/* Gets the code to include on netlogo code. */
			codeToInclude = functionItertype.getDeclarations().toString();
			
			/* Sets the intertype type. */
			type = IntertypeType.DECLARATION;
			
		}else if(intertype instanceof ALocalVarIntertype){ 
			/* Cast to the correct object. */
			ALocalVarIntertype functionItertype = (ALocalVarIntertype) intertype;
			
			/* Gets the code to include on netlogo code. */
			codeToInclude = functionItertype.getLetDeclaration().toString();
			
			/* Gets the specified local to include code. */ 
			placeToInclude = functionItertype.getIdentifier().toString().trim();
			
			if(functionItertype.getFormals()!=null){
				/* Gets the specified local formals to include code. */ 
				placeToIncludeFormals = functionItertype.getFormals().toString();
			}
			
			/* Sets the intertype type. */
			type = IntertypeType.VAR;
			
		}else{
			/* Cast to the correct object. */
			AGlobalVarIntertype functionItertype = (AGlobalVarIntertype) intertype;
			
			/* Gets the code to include on netlogo code. */
			codeToInclude = functionItertype.getIdentifier().toString();
			
			/* Gets the declaration to add intertype. */
			placeToInclude = Globals.extractIdReferingFromIntertypeDeclaration( functionItertype.getDeclarations().toString() );
			
			/* Sets the intertype type. */
			type = IntertypeType.GLOBAL;
		}
			
		/* Creates a intertype object and returns it. */
		return new Intertype(codeToInclude, placeToInclude, new Formals(placeToIncludeFormals), type);
	}

	public String getIdentifier() {
		return identifier;
	}

	public void setIdentifier(String identifier) {
		this.identifier = identifier;
	}

	public List<PComponentAspect> getListAspectComponentFromInputFile() {
		return listAspectComponentFromInputFile;
	}

	public void setListAspectComponentFromInputFile(
			List<PComponentAspect> listAspectComponentFromInputFile) {
		this.listAspectComponentFromInputFile = listAspectComponentFromInputFile;
	}

	public AspectComponents getAspectComponents() {
		return aspectComponents;
	}

	public void setAspectComponents(AspectComponents aspectComponents) {
		this.aspectComponents = aspectComponents;
	}
}
